package com.fishingwithme.controllers;

import com.fishingwithme.domain.anybiting.MessageType;
import com.fishingwithme.domain.anybiting.SubType;
import com.fishingwithme.domain.circle.CircleRedisMessagePublisher;
import com.fishingwithme.domain.common.Location;
import com.fishingwithme.domain.spots.Spot;
import com.fishingwithme.domain.spots.SpotService;
import cn.fishingwithme.infrastructure.dtos.*;
import com.fishingwithme.infrastructure.dtos.*;
import com.fishingwithme.infrastructure.dtos.anybiting.BaseCircleMessageDto;
import com.fishingwithme.infrastructure.utils.CommonUtils;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

import static com.fishingwithme.infrastructure.cfg.RedisConfig.CIRCLE_MESSAGE_CHANNEL;

@Controller
@RequestMapping("/spot")
@Tag(name = "Fishing Spots", description = "APIs for managing fishing spots, harvests, and related operations")
public class SpotController {
    @Autowired
    private SpotService spotService;
    @Autowired
    private CircleRedisMessagePublisher circleRedisMessagePublisher;

    @GetMapping(path = "/getHarvest")
    public @ResponseBody SpotHarvestDto getHarvest(@RequestParam Long id) {
        return spotService.getSpotHarvest(id);
    }

    @GetMapping(path = "/getComments")
    public @ResponseBody IPage<SpotVisitHisDto> getVisitHis(
            @RequestParam(required = false, defaultValue = "1") Integer pageNum,
            @RequestParam(required = false, defaultValue = "10") Integer pageSize,
            @RequestParam("spotId") Long spotId) {
        if (pageNum < 1)
            pageNum = 1;
        if (pageSize < 1 || pageSize > 100)
            pageSize = 20;

        return spotService.getSpotVisitHis(pageNum, pageSize, spotId);
    }

    @GetMapping(path = "/getUserMgrSpots")
    public @ResponseBody IPage<SpotQueryResDto> listSpots(
            @RequestParam(required = false, defaultValue = "1") Integer pageNum,
            @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
        // 参数校验
        if (pageNum < 1)
            pageNum = 1;
        if (pageSize < 1 || pageSize > 100)
            pageSize = 20;

        // 直接调用服务，无需传递fisherId
        return spotService.listCollectedSpots(pageNum, pageSize);
    }

    @PostMapping(path = "/querySpots", produces = "application/json", consumes = "application/json")
    public @ResponseBody List<SpotQueryResDto> querySpots(@RequestBody SpotQueryDto dto) {
        return spotService.searchSpots(dto);
    }

    @GetMapping(path = "/getSpotNavLine", produces = "application/json")
    public @ResponseBody SpotNavDto getSpotsNavLine(@RequestParam Long spotId) {
        return spotService.getSpotNavLineById(spotId);
    }

    @GetMapping(path = "/getSpotsByLocation", produces = "application/json")
    @Operation(
            summary = "Get fishing spots by location",
            description = "Retrieve fishing spots within a specified distance from given coordinates"
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Fishing spots retrieved successfully",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = SpotDashboardDto.class)
                    )
            )
    })
    public @ResponseBody List<SpotDashboardDto> getSpotsByLocation(
            @Parameter(description = "Latitude coordinate", required = true, example = "39.9042")
            @RequestParam Double latitude,
            @Parameter(description = "Longitude coordinate", required = true, example = "116.4074")
            @RequestParam Double longitude,
            @Parameter(description = "Search distance in meters (max 30000)", example = "5000")
            @RequestParam(defaultValue = "30000") Integer distance) {
        Integer newDistance = distance;
        if (distance > 30000) {
            newDistance = 30000;
        }
        return spotService.getAllSpotsByLocation(new Location(latitude, longitude), newDistance);
    }

    @GetMapping("/getUserSpots")
    public @ResponseBody List<SpotDashboardDto> getUserSpotsByLocations(@RequestParam Double latitude,
            @RequestParam Double longitude, @RequestParam(defaultValue = "30000") Integer distance) {
        return spotService.selectMySpotsByLocationAndDistance(new Location(latitude, longitude), distance);
    }

    @GetMapping("/getSecretSpots")
    public @ResponseBody List<SpotDashboardDto> getSecretSpots(@RequestParam Double latitude,
            @RequestParam Double longitude, @RequestParam(defaultValue = "30000") Integer distance) {
        return spotService.selectSecretSpotsByLocationAndDistance(new Location(latitude, longitude), distance);
    }

    @PostMapping(value = "/checkSpotExists", produces = "application/json", consumes = "application/json")
    public @ResponseBody DTO<Boolean> checkSpotExists(@RequestBody Location location) {
        return spotService.checkSpotExists(location, CommonUtils.SPOT_DEFAULT_DISTANCE);
    }

    @PostMapping(value = "/addSpot", produces = "application/json", consumes = "application/json")
    @Operation(
            summary = "Add new fishing spot",
            description = "Create a new fishing spot with detailed information",
            security = @SecurityRequirement(name = "Bearer Authentication")
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Fishing spot created successfully",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = DTO.class)
                    )
            ),
            @ApiResponse(
                    responseCode = "400",
                    description = "Invalid spot data"
            ),
            @ApiResponse(
                    responseCode = "401",
                    description = "Unauthorized - JWT token required"
            )
    })
    public @ResponseBody DTO<Long> addSpot(
            @Parameter(description = "Fishing spot information", required = true)
            @RequestBody SpotDto dto) {
        Spot spot = spotService.addSpot(dto);

        BaseCircleMessageDto msg = BaseCircleMessageDto.builder()
                .type(MessageType.EVT)
                .subtype(SubType.EVT_N_SPOT)
                .cityCode(spot.getCityCode())
                .bizId(spot.getId())
                .priority(0)
                .timestamp(CommonUtils.now())
                .bizId(spot.getId())
                .sender("system")
                .build();
        circleRedisMessagePublisher.publish(CIRCLE_MESSAGE_CHANNEL, msg);

        return DTO.success(spot.getId());
    }

    @PostMapping(value = "/updateSpot", produces = "application/json", consumes = "application/json")
    public @ResponseBody void updateSpot(@RequestBody FishingSpotUpdateDto dto) {
        spotService.updateSpot(dto);
    }

    @PostMapping(value = "/addVisitHarvest", produces = "application/json", consumes = "application/json")
    public @ResponseBody void addSpotVisitHarvest(@RequestBody VisitHarvestDto dto) {
        VisitHarvestDto savedDto = spotService.addVisitHarvest(dto);
        if (dto.getId() != null) {
            BaseCircleMessageDto msg = BaseCircleMessageDto.builder()
                    .type(MessageType.EVT)
                    .subtype(SubType.EVT_SHOW_H)
                    .priority(0)
                    .cityCode(savedDto.getCityCode())
                    .bizId(savedDto.getId())
                    .timestamp(CommonUtils.now())
                    .sender("system")
                    .build();
            circleRedisMessagePublisher.publish(CIRCLE_MESSAGE_CHANNEL, msg);
        }

    }

    @GetMapping(value = "/collect", produces = "application/json")
    public @ResponseBody void collectSpot(@RequestParam Long spotId) {
        spotService.collectSpot(spotId);
    }

    @GetMapping(value = "/getProps", produces = "application/json")
    public @ResponseBody SpotProps getProps() {
        return spotService.getProps();
    }

    @GetMapping(value = "/getSpot", produces = "application/json")
    public @ResponseBody SpotDetailDto getSpot(@RequestParam Long spotId) {
        return spotService.getSpot(spotId);
    }

    @GetMapping(value = "/getUpdateSpot", produces = "application/json")
    public @ResponseBody SpotEnhanceDto getUpdateSpot(@RequestParam Long spotId) {
        return spotService.getUpdateSpot(spotId);
    }

    @GetMapping(value = "/getLatest", produces = "application/json")
    public @ResponseBody UserSpotDto getSpot() {
        return spotService.getSpot();
    }

    @GetMapping(value = "/getUserSpot", produces = "application/json")
    public @ResponseBody UserSpotDto getUserSpot(@RequestParam Long spotId) {
        return spotService.getUserSpot(spotId);
    }

    @GetMapping("/getSpotHarvests")
    public @ResponseBody IPage<SpotHarvestDto> getSpotHarvest(@RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize,
            @RequestParam Long spotId) {
        if (pageNum < 1)
            pageNum = 1;
        if (pageSize < 1 || pageSize > 100)
            pageSize = 20;
        return spotService.getSpotHarvests(pageNum, pageSize, spotId);
    }

    @GetMapping(path = "/collected", produces = "application/json")
    public @ResponseBody IPage<SpotPageDto> getCollectedSpots(
            @RequestParam(required = false) Boolean isCollected,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        if (pageNum < 1)
            pageNum = 1;
        if (pageSize < 1 || pageSize > 100)
            pageSize = 20;
        Long userId = CommonUtils.getUserId();
        return spotService.getCollectedSpots(userId, isCollected, pageNum, pageSize);
    }

    @GetMapping(path = "/public/collected", produces = "application/json")
    public @ResponseBody IPage<SpotCollectPageDto> getPublicCollectedSpots(
            @RequestParam(required = false) Boolean isCollected,
            @RequestParam Long spotId,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        if (pageNum < 1)
            pageNum = 1;
        if (pageSize < 1 || pageSize > 100)
            pageSize = 20;
        return spotService.getPublicCollectedSpots(isCollected,spotId, pageNum, pageSize);
    }

    @GetMapping(value = "/harvest/likes", produces = "application/json")
    public @ResponseBody DTO<Boolean> likeHarvest(@RequestParam Long harvestId) {
        String type = SpotHarvestDto.LIKE_TYPE;
        return DTO.success(spotService.harvestLikes(type, harvestId));
    }

    @GetMapping(value = "/harvest/dislike", produces = "application/json")
    public @ResponseBody DTO<Boolean> dislikeHarvest(@RequestParam Long harvestId) {
        String type = SpotHarvestDto.LIKE_TYPE;
        return DTO.success(spotService.harvestDislike(type, harvestId));
    }

    @GetMapping(path = "/search", produces = "application/json")
    @Operation(
            summary = "Search fishing spots",
            description = "Search for fishing spots by query string and city code"
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Search results retrieved successfully",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = DTO.class)
                    )
            )
    })
    public @ResponseBody DTO<List<SearchResponseDto>> search(
            @Parameter(description = "Search query string", required = true, example = "lake")
            @RequestParam String queryStr,
            @Parameter(description = "City code for filtering", required = true, example = "110000")
            @RequestParam String cityCode) {
        return DTO.success(spotService.search(queryStr, cityCode));
    }

    /**
     * 分页查询钓点列表，支持动态条件筛选
     *
     * @param pageNum     页码，默认1
     * @param pageSize    每页数量，默认10
     * @param isFree      是否收费(0-否,1-是)
     * @param cityCode    城市代码
     * @param isPrivate   是否秘密钓点(0-否,1-是)
     * @param isForbidden 是否禁止钓鱼(0-否,1-是)
     * @param query       钓点名称模糊查询
     * @return 分页钓点列表
     */
    @GetMapping("/list")
    public @ResponseBody DTO<IPage<SpotListDto>> listSpots(
            @RequestParam(required = false, defaultValue = "1") Integer pageNum,
            @RequestParam(required = false, defaultValue = "10") Integer pageSize,
            @RequestParam(required = false) Boolean isFree,
            @RequestParam(required = false) String cityCode,
            @RequestParam(required = false) Boolean isPrivate,
            @RequestParam(required = false) Boolean isForbidden,
            @RequestParam(required = false) String query) {

        IPage<SpotListDto> page = spotService.listSpots(pageNum, pageSize, isFree, cityCode, isPrivate, isForbidden,
                query);
        return DTO.success(page);
    }

    @PostMapping(path = "/{spotId}/toggle")
    public @ResponseBody DTO<Boolean> toggleSpotStatus(@PathVariable Long spotId, @RequestParam Boolean disabled) {
        spotService.toggleSpotStatus(spotId, disabled);
        return DTO.success(true);
    }
}
